package com.gitee.l0km.javadocreader;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.gitee.l0km.com4j.base.MiscellaneousUtils;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.RootDoc;

/**
 * 读取指定java源文件的javadoc信息
 * @author guyadong
 *
 */
@SuppressWarnings("restriction")
public class JavadocReader {
	protected static final Logger logger = LoggerFactory.getLogger(JavadocReader.class);
	private volatile static RootDoc root = null;
	private static String sourcepath;
	private static String[] sourcepathList = new String[0];
	private static String classpath;

	public static  class Doclet {
	    public static boolean start(RootDoc root) {
	    	JavadocReader.root = root;
	        return true;
	    }
	}
	public static void show(){
		if(null == root){
			return ;
		}
        ClassDoc[] classes = root.classes();
        for (int i = 0; i < classes.length; ++i) {
        	new ExtClassDoc(classes[i]).output(System.out);
        }
	}
	private static RootDoc getRoot() {
		if(null == root) {
			synchronized (JavadocReader.class) {
				if(null == root) {
					readDocs(null, classpath, sourcepath);
				}
			}
		}
		return root;
	}

	/**
	 * @since 2.0.1
	 */
	public static void resetRoot() {
		root = null;
	}
	/**
	 * 解析指定的java源文件返回javadoc对象 {@link RootDoc}<br>
	 * 参见 <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/standard-doclet.html#runningprogrammatically">Running the Standard Doclet Programmatically</a>
	 * @param source a java source file or package name
	 * @param classpath value for  '-classpath',{@code source}的class位置,可为{@code null},如果不提供,无法获取到完整的注释信息(比如annotation)
	 * @param sourcepath value for '-sourcepath'
	 * @return {@link RootDoc}对象
	 */
	public synchronized static RootDoc readDocs(String source, String classpath,String sourcepath) {
		List<String> args = Lists.newArrayList("-doclet", 
				Doclet.class.getName(), "-quiet","-Xmaxerrs","1","-Xmaxwarns","1","-encoding","utf-8","-private");
		if(Strings.isNullOrEmpty(source) || !source.endsWith(".java")) {
			/** source参数为包名或为空时，指定-subpackages参数,获取所有类注解 */
			args.add("-subpackages");
			args.add(subpackages(sourcepath));
		}
		if(!Strings.isNullOrEmpty(classpath)){
			args.add("-classpath");
			args.add(normailzePathSeparator(classpath));
		}
		if(!Strings.isNullOrEmpty(sourcepath)){
			args.add("-sourcepath");
			args.add(normailzePathSeparator(sourcepath));
		}
		if(!Strings.isNullOrEmpty(source)) {
			args.add(source);
		}
		logger.debug("Javadoc Options:\n<<<<\n{}\n>>>>",Joiner.on("\n").join(args));
		int returnCode = com.sun.tools.javadoc.Main.execute(JavadocReader.class.getClassLoader(),args.toArray(new String[args.size()]));
		if(0 != returnCode){
			logger.warn("javadoc ERROR CODE = {}", returnCode);
			throw new IllegalStateException();
		}
		return root;
	}
	/**
	 * 读取{@code source}指定源文件的注释
	 * @param source
	 * @param classpath
	 * @param sourcepath
	 * @return 读取失败返回{@code null}
	 */
	public synchronized static ExtClassDoc read(String source, String classpath,String sourcepath) {
		try {
			return new ExtClassDoc(readDocs(source, classpath, sourcepath).classes()[0]);	
		} catch (IllegalStateException e) {
			return null;
		}catch (Exception e) {
			e.printStackTrace();
			return null;
		}		
	}
	/** @see #read(String, String) */
	public static ExtClassDoc read(String source) {
		return read(source,classpath);
	}
	public static ExtClassDoc read(String prefix,Class<?> clazz) {
		return read(getSourceFile(prefix,clazz),classpath);
	}
	
	/**
	 * 根据类名查找注释对象
	 * @param qualifiedName 带包名的类全名
	 * @since 3.1.4
	 */
	@AoWeakCacheable(maximumSize = 100, expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static ExtClassDoc readQualifiedName(String qualifiedName) {
		if (null != qualifiedName) {
			try {
				if (getRoot() == null) {
					return null;
				}
				ClassDoc classDoc = getRoot().classNamed(qualifiedName);
				if (null != classDoc) {
					return new ExtClassDoc(classDoc);
				}
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
		return null;
	}
	private static ExtClassDoc read0(Class<?> clazz) {
		try{
			if(getRoot() == null){
				return null;
			}	
			String qualifiedName = clazz.getName().replace('$', '.');
			return readQualifiedName(qualifiedName);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	public static ExtClassDoc read(Class<?> clazz) {
		return read0(clazz);
	}
	public static ExtClassDoc read(String source, List<String> classpath) {
		return read(source,null == classpath?null:Joiner.on(File.pathSeparator).skipNulls().join(classpath));
	}
	public static ExtClassDoc read(String source, String[] classpath) {
		return read(source,null == classpath?null:Joiner.on(File.pathSeparator).skipNulls().join(classpath));
	}
	public synchronized static ExtClassDoc read(String source, String classpath) {
		return read(source,classpath,sourcepath);
	}
	public static ExtClassDoc read(String source, List<String> classpath,List<String> sourcepath) {
		return read(source,
				null == classpath ? null : Joiner.on(File.pathSeparator).skipNulls().join(classpath),
				null == sourcepath ? null : Joiner.on(File.pathSeparator).skipNulls().join(sourcepath));
	}
	public static ExtClassDoc read(String source, String[] classpath,String[] sourcepath) {
		return read(source,
				null == classpath ? null : Joiner.on(File.pathSeparator).skipNulls().join(classpath),
				null == sourcepath ? null : Joiner.on(File.pathSeparator).skipNulls().join(sourcepath));
	}
	/**
	 * 返回类的源文件位置
	 * @param prefix 源文件夹,可为{@code null}
	 * @param clazz 为{@code null}则返回{@code null}
	 * @return 返回'/'分隔'.java'结尾的路径名,for example, '/home/src/java/awt/Graphics*java‘
	 */
	public static String getSourceFile(String prefix,Class<?> clazz){
		if(null != clazz ){
			Class<?> innerClass = clazz;
			Class<?> declaringClass = innerClass.getDeclaringClass();
			while(declaringClass != null){
				innerClass = declaringClass;
				declaringClass = declaringClass.getDeclaringClass();
			}
			String source = innerClass.getName().replace('.', File.separatorChar) + ".java";
			return prefix == null ? source : prefix + File.separator + source;
		}
		return null;
	}

	/**
	 * 在{@link #setSourcepath(String)}指定的源码路径中搜索类的源码文件
	 * @param clazz
	 * @return source file path or null if clazz is null or not found  
	 */
	public static String findSourceFileInSourcepathList(Class<?> clazz){
		if(null == clazz) {
			return null;
		}
		for(String path:sourcepathList){
			String source = getSourceFile(path, clazz);
			if(new File(source).isFile()){
				return source;
			}
		}
		return null;
	}
	/**
	 * 返回是否存在指定类的源码,{@code clazz}为{@code null}返回{@code null}
	 * @param clazz
	 * @see #findSourceFileInSourcepathList(Class)
	 */
	public static boolean existSourceOf(Class<?> clazz) {
		String file = findSourceFileInSourcepathList(clazz);
		return null != file;
	}
	/**
	 * 返回类的源码位置路径
	 * @param clazz
	 * @return file path or null if clazz is null
	 * @see #getSourceFile(String, Class)
	 */
	public static String getSourceFile(Class<?> clazz){
		if(null == clazz) {
			return null;
		}
		String found = findSourceFileInSourcepathList(clazz);
		if(null != found) {
			return found;
		}
		return getSourceFile(null, clazz);
	}
	
	/**
	 * @return sourcepath
	 */
	public static String getSourcepath() {
		return sourcepath;
	}
	/**
	 * @param sourcepath 要设置的 sourcepath
	 */
	public static void setSourcepath(String sourcepath) {
		JavadocReader.sourcepath = sourcepath;
		JavadocReader.sourcepathList = asPaths(sourcepath).toArray(new String[0]);
	}
	/**
	 * @return classpath
	 */
	public static String getClasspath() {
		return classpath;
	}
	/**
	 * @param classpath 要设置的 classpath
	 */
	public static void setClasspath(String classpath) {
		JavadocReader.classpath = classpath;
	}
	
	/**
	 * 归一化输入的路径列表中的路径分隔符<br>
	 * 则将字符串中的';,:'替换为系统路径分割符({@link File#pathSeparator})
	 * @param input ';,:'分割的路径列表
	 * @return 返回归一化的字符串,输入为{@code null}则返回 {@code null}
	 */
	private static String normailzePathSeparator(String input) {
		return Joiner.on(File.pathSeparator).join(asPaths(input));
	}
	/**
	 * 将';,:'分割的字符串拆分为路径列表<br>
	 * @param input ';,:'分割的路径列表
	 * @return 返回字符串列表,输入为{@code null}则返回空表
	 */
	private static List<String> asPaths(String input) {
		if(!Strings.isNullOrEmpty(input)){
			List<String> paths;
			if (File.pathSeparatorChar == ':') {
				paths = MiscellaneousUtils.elementsOf(input,";,:");
			}else {
				paths = MiscellaneousUtils.elementsOf(input,";,");
			}
			return FluentIterable.from(paths).transform(new Function<String, String>() {

				@Override
				public String apply(String input) {
					return input.trim();
				}
			}).toList();
		}
		return Collections.emptyList();
	}
	private static String subpackages(String sourcepath) {
		FluentIterable<String> subpackages = FluentIterable.of();
		for(String p:asPaths(sourcepath)) {
			File path = new File(p);
			if(path.isDirectory()){
				subpackages = subpackages.append(path.list(DIR_FILTER));
			}
		}
		// 包名冒号分割
		// 参见 https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDJEDJI
		return Joiner.on(":").join(subpackages.copyInto(new TreeSet<>()));
		
	}
	private static final FilenameFilter DIR_FILTER = new FilenameFilter() {

		@Override
		public boolean accept(File dir, String name) {
			return new File(dir,name).isDirectory();
		}};
}
